#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2025/6/17 09:45
# @Author  : Chenmm
# @File    : user_method.py
# @Software: PyCharm


'''
# 注册的逻辑
1.校验参数是否合规（这里我使用的pydantic自身的校验，见user_schemas）

2.判断用户名是否已存在

3.加密密码

4.保存到数据库
'''

from sqlalchemy.orm import Session
from passlib.context import CryptContext
from traceback import format_exc
from datetime import timedelta

from models.models import *
from models.user_schema import *
from common.json_tools import *
from common.log import logger
from middlewares.mysql.database import create_db

from jose import jwt, JWTError
from settings.config import TOKEN_CONFIG
from fastapi import Request, Header, Depends, HTTPException




pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)



def get_user_by_uid(db: Session, user_id: int):
    return db.query(User).filter(User.id == user_id, User.status == 0).first()


def get_user_by_username(db: Session, username: str):
    return db.query(User).filter(User.username == username, User.status == 0).first()


# 注册用户逻辑
def db_create_user(db: Session, user: UserCreate):
    logger.info("创建用户开始")
    db_user = get_user_by_username(db, user.username)
    if db_user:
        return response(code=100104, message="用户名已存在")
    if (user.role == "学生" and user.student_num is None) or (user.role == "老师" and user.job_num is None):
        return response(code=100102, message="身份和对应号不匹配")

    try:
        user.password = get_password_hash(user.password)
    except:
        logger.warning(f"method db_create_user error: {format_exc()}")
        return response(code=100105, message="密码加密失败")

    user_to_db = User(**user.model_dump())
    db_role = db.query(Role).filter(Role.name == user.role.value).first()
    user_to_db.role = db_role.id

    try:
        db.add(user_to_db)
        db.commit()  # 提交保存到数据库
        db.refresh(user_to_db)  # 刷新
        logger.info(f"创建用户:'{user.username}'成功")
    except:
        logger.warning(f"method db_create_user error: {format_exc()}")
        return response(code=100101, message="注册失败，请重试")
    return response(code=200, data=user_to_db.username)


# def create_access_token(data: dict):
#     """产生token"""
#     to_encode = data.copy()
#     encoded_jwt = jwt.encode(to_encode, TOKEN_CONFIG['secret_key'], algorithm=TOKEN_CONFIG['algorithm'])
#     return encoded_jwt


def create_access_token(data: dict, expires: Optional[timedelta] = None):
    """优化创建token的方法，携带过期时间，保证每次刷新产生的token不一致"""
    to_encode = data.copy()
    if expires:
        expire_time = datetime.now() + expires
    else:
        expire_time = datetime.now() + timedelta(minutes=TOKEN_CONFIG["access_token_expire_time"])
    to_encode.update({"exp": expire_time})
    encoded_jwt = jwt.encode(to_encode, TOKEN_CONFIG['secret_key'], algorithm=TOKEN_CONFIG['algorithm'])
    return encoded_jwt





# 登录逻辑
async def verify_login(request: Request, user: UserLogin, db: Session):
    logger.info("登录开始了")
    db_user = get_user_by_username(db, user.username)
    if not db_user:
        logger.warning(f"用户：'{user.username}'不存在")
        return response(code=100205, message="用户不存在")

    # 先判断60分钟内登录错误次数的限制，如果60分钟内登录错误次数大于10，则不用验证密码，直接返回100204：输入密码错误次数过多，账号暂时锁定，请60分钟后再来登录
    error_key = user.username + "_password"
    result = await request.app.state.redis.hgetall(error_key, encoding="utf-8")
    if result:
        error_num = int(result["num"])
        num_time = (datetime.now() - datetime.strptime(result["time"], "%Y-%m-%d %H:%M:%S")).seconds / 60
        if error_num > 10 and num_time < 60:
            return response(code=100207, message="输入密码错误次数过多，账号已暂时锁定，请60分钟后再来登录")
    ###########
    verify = verify_password(user.password, db_user.password)
    if verify:
        # 清空60分钟内登录错误次数的限制
        error_key = user.username + "_password"
        result = await request.app.state.redis.hgetall(error_key, encoding="utf-8")
        if result:
            await request.app.state.redis.delete(error_key)
        ###
        redis_user = await request.app.state.redis.get(user.username)
        try:
            token = create_access_token(data={"sub": user.username})
        except:
            logger.warning(f"method verify_login error: {format_exc()}")
            return response(code=100203, message="生产token失败")
        await request.app.state.redis.set(user.username, token, expire=TOKEN_CONFIG["access_token_expire_time"] * 60)
        logger.info("登录成功")
        return response(data={"token": token})
    else:
        # 60分钟内失败次数大于等于5次，60分钟内不能登陆
        error_key = user.username + "_password"
        result = await request.app.state.redis.hgetall(error_key, encoding="utf-8")
        # 没有查到认为是第一次出现错误，将次数设置为1，时间设置为当前时间
        if not result:
            current_time = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
            await request.app.state.redis.hmset_dict(error_key, num=1, time=current_time)
            return response(code=100206, message="密码错误")
        # 查到则不是第一次，要分多重情况
        else:
            error_num = int(result["num"])
            num_time = (datetime.now() - datetime.strptime(result["time"], "%Y-%m-%d %H:%M:%S")).seconds / 60
            # 60分钟内错误没达到5次，错误次数加1
            if error_num < 5 and num_time < 60:
                error_num += 1
                await request.app.state.redis.hmset_dict(error_key, num=error_num)
                return response(code=100207, message="密码错误")
            # 超60分钟没有达到5次，错误次数重置为1，时间设置为当前
            elif error_num < 5 and num_time > 60:
                error_num = 1
                num_time = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
                await request.app.state.redis.hmset_dict(error_key, num=error_num, time=num_time)
                return response(code=100206, message="密码错误")
            # 60分钟内错误超过5次，错误次数加1，限制60分钟内不可以登录
            elif error_num >= 5 and num_time < 60:
                error_num += 1
                await request.app.state.redis.hmset_dict(error_key, num=error_num)
                return response(code=100204, message=f"输入密码错误次数过多，如果继续连续错误{11-error_num}次，账号将被暂时锁定60分钟")
            # 超60分钟，如果再次输错，将错误次数重置为1，时间设置为当前时间
            else:
                error_num = 1
                num_time = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
                await request.app.state.redis.hmset_dict(error_key, num=error_num, time=num_time)
                return response(code=100206, message="密码错误")


def get_role_name(db: Session, id: int):
    return db.query(Role).filter(Role.id == id).first()


async def get_current_user(request: Request, token: str = Header(...), db: Session = Depends(create_db)) -> UsernameRole:
    """校验用户是否登录并获取用户信息"""
    logger.info("获取个人信息开始了")
    credentials_exc = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="验证失败"
    )
    credentials_expired_exc = HTTPException(
        status_code=status.HTTP_403_FORBIDDEN,
        detail="用户未登录或者登录token已经失效"
    )
    try:
        payload = jwt.decode(token, TOKEN_CONFIG["secret_key"], algorithms=[TOKEN_CONFIG["algorithm"]])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exc
        redis_token = await request.app.state.redis.get(username)
        if not redis_token or redis_token != token:
            raise credentials_expired_exc
        user_role = get_role_name(db, get_user_by_username(db, username).role).name
        return UsernameRole(username=username, role=user_role)
    except JWTError:
        logger.warning(f"method get_current_user error: {format_exc()}")
        raise credentials_exc


async def change_password_method(
        request: Request, param_password: UserChangePassword, _user: UsernameRole, db: Session):
    """修改密码"""
    logger.info(f"用户：{_user.username} 修改密码开始了")
    if param_password.password == param_password.new_password:
        return response(code=100304, message="新旧密码不能一样")
    if len(param_password.new_password) < 8 or len(param_password.new_password) > 16:
        return response(code=100303, message="新密码长度不匹配")
    db_user = get_user_by_username(db, _user.username)
    verify = verify_password(param_password.password, db_user.password)
    if verify:
        new_password_hash = get_password_hash(param_password.new_password)
        db_user.password = new_password_hash
        try:
            db.commit()
            db.refresh(db_user)
        except:
            logger.warning(f"method change_password_method error: {format_exc()}")
            return response(code=100302, message="修改密码失败")
        await request.app.state.redis.delete(_user.username)
        await request.app.state.redis.delete(_user.username + "_password")
        logger.info(f"用户：{_user.username} 修改密码成功")
        return response(data="密码修改成功，请重新登录")
    return response(code=100301, message="原始密码未校验通过")